Problem Description

Monitoring of the natural environment has always been of interest to human civilisation. Weather in particular, affects our day to day lives and even can affect our health and business interests. Remote sensing from arial vehicles and satellites combined with supercomputing resources provide us with readily available high accurate weather forecasts and current condition snapshots, while, on-ground weather stations fill in the gaps and provide actual mesurements of conditions as opposed to predictions.

It is a fact that local weather systems and effects can create micro-climates that can result in significantly different wind patterns in some areas than that which is predicted from the aforementioned remote sensing. As a result, weather stations remain a very valuable tool for understanding weather systems. Unfortunately, there may not be a weather station in the particular area that detailed weather observations are desired. This is esspecially acute an issue in developing countries where there are even fewer of weather stations than are found in developed countries. Take Bali, Indonesia for example, there are a handful (2-3) operational weather stations on the island with data available on the internet. There are parts of the island that have strong local diurnal weather effects that can lead to local winds being completly different to the prevailing trade winds that blow accross the open ocean. These differences are strongest in the morning to midday, the strength of the local effects and how long the differences last for are unpredictable, somedays they can be weak, other days they are strong and last for a few hours, other days they are strong and last from early morning into the early afternoon, other days the local wind remains calm all day while the open ocean tradewinds are stong.

The solution is to build your own weather station and deploy it, primarily for measuring wind speed and direction. The requirement is that the station be:


Description of the Solution

There are some cheap commercial weather stations that can perform this task, however none have communciations ability that enables them to be deplyed away from a home. Typically most come with a custom short range radio link connecting the outdoor station to an indoor receiver unit, mainly targeted for home use. These cheap systems cost around $150 upwards, which is not that cheap. Professional stations offer more reliability and communicationa options, however they get very expensive, costing 1000s of dollars.

The solution proposed here is a bare-bones, cheap and reliable weather station that can connect to either wifi or the telecommunications network. The prototype presented has only got wifi conenctivity currently, but adding gsm/wcdma/lte connectivity would be relatively easy, although the power considerations would be challenging.

Systems Level Architecture

The proposed solution is a simple wind speed and wind direction meter mounted on a wooden frame. These sensors are connected to a micro-controller unit (MCU) that is located in an insultated weather-proof enclosure. The weather-proof enclosure houses an ATMEGA328P MCU along with a 3.7 LiPo battery, a 5v solar panel, a solar/battery power manager/regulator that controls charging the battery from the solar panel as well as supplying a constant 5v to the MCU and the peripherals. The peripherals consist of a 5v to 3.5v DCDC converter and an ESP8266 wifi board. This setup is shown in the following figure.

Fig.1 - System Level Architecture

Fig.1 - System Level Architecture

Operation

The MCU has 3 phases:

  1. The MCU powers the sensors and takes readings for around 30s. The current draw in this phase is around 30mA
  2. The MCU powers off the sensors, powers up the WiFi board and sends data to the server ~ 30s. The current draw in this phase is around 150mA, however there is also a high transient current spike apon ESP8266 power on which can cause the supply voltage to the ATMEGA328P to dip momentarily, this issue needed to be managed, in our case, activating the 2.7v brow-out reset function as well as placing capacitors over the supply lines to the ATMEGA328P seemed to fix it.
  3. The MCU powers off the WiFi board and goes into a deep sleep mode for a varying amount of time depending on the ambient light level. The sleep time ranges from 5 minutes daytime to 15 minutes night time as wind readings are not as interesting at night. The current usage in this mode was ~ 4mA, it should have been lower, but there was no time to investigate. However 4mA was still sufficiently low.

The MCU will simply cycle through these 3 phases indefintely. As can be seen from the performance data later in the report, the system was able to harvest suffient solar energy to recharge the battery, even on cloudy/stormy days. On sunny days, the battery would fill within 2-3 hours.


Implementation Details

Schematic

The following figure depicts the connections for the entire station.

Fig.5 - Connections for the ATMEGA328P

Fig.5 - Connections for the ATMEGA328P

Fig.x - Actual Layout

Fig.x - Actual Layout

Micro-Controller

The system is controlled by the ATMEGA328P microcontroller chip in a breadboard. The MCU chip is the same as that found in an Arduino Uno board, however bought standalone along with the crystal oscillator it typically costs around $5-8. This was done to reduce the power consumption as the Arduino Uno board contains alot of uncontrollable peripheral hardware (FTDI chip, voltage regulators). Using the Arduino Uno board the power consumption is ~ 50mA at 5v while active and 25mA at 5v while in sleep mode. Using just the ATMEGA328P, the power consumption is around 30mA at 5v while active and 1mA at 5v in sleep mode.

Fig.4 - Breadboard with ATMEGA328P

Fig.4 - Breadboard with ATMEGA328P

Connection of the standalone ATMEGA328P:

  • the MCU needs to be driven by a crystal oscillator with ground tied 22pF ceramic capacitors on both legs. It should be placed as close to the MCU pins as possible
Fig.x - Crystal Oscillator

Fig.x - Crystal Oscillator

  • Both pairs of VCC and GND pins also need to be decoupled by large ceramic or film capacitors to minimise power line noise. In this case 1uF capacitors where used along with smaller 100nF capacitors place as close to the MCU pins as possible. Read this blog for more information, however it is confirmed that this is critical for the system to operate [5].
  • The reset pin needs to be tied to VCC through a 10K pullup resistor.
  • The data tx/rx pins (9,10) to the ESP8266 are using software serial as the hardware serial pins (0,1) are needed for uploading sketches. The tx pin (10) in particular needs to use a voltage divider (10k pullup resistor and 20k pulldown resistor) to drop the 5v output to 3.3v for the ESP8266. The rx pin (9) doesn’t need anything as it can read the incoming 3.3v from the ESP8266 ok. The baud rate used for software serial was 9600bps, this had to be first set on the ESP8266 as by default it uses 115200, which is too fast for software serial. Programming the ESP8266 had to be done by using the arduino uno board and connecting it to the tx/rx pins.
  • the wind speed sensor works by closing a circuit evey half turn, thus it had to be measured by connecting the output line from the sensor to a digital pin (with an internal 20K pullup resistor) and activating a falling level interrupt on that pin, i.e. everytime the wind speed sensor circuit closed pulling the pin to ground, the interrupt would trigger and an interrupt handling routing would measure the time since the last interrupt and thus log the current wind speed [8].
  • the wind direction sensor output line was connected to an analog input pin and tied to high via a 10K pullup resistor. For each of 16 available azimuths, the resistance of the sensor would change, thus we had to measure the voltage and convert it to an azimuth value through some lookup functions.
  • the battery level, temperature and ambient light sensors all were connected to analog input pins and tied to high via 10K pullup resistors.
  • one digital pin was used to control power to the wifi board, not to supply the power though, only to signal to the regulator to activate the power.
  • One digital pin was used to control and supply power to the wind direction sensor and one pin was used to supply power to the wind speed sensor as these devices had very low current draw this was ok.

Uploaded sketches to the ATMEGA328P via an Arduino Uno

An Arduino Uno (without chip) is required to upload sketches to the ATMEGA328P. First you need the following connection.

Fig.x - ATMEGA328P Connection for sketch Uploading

Fig.x - ATMEGA328P Connection for sketch Uploading

Then you need to set the board type to Arduino Nano instead of Arduino Uno and the processor type to ATMEGA328P and you are ok to upload the sketch. Sometimes you need to put the ATMEGA328P chip back in the Arduino Uno board and upload a sketch first if you have recently changed the bootloader or any of the low/high.extended byte fuses, then you are good to use this external method.

This is a good guide [6].

Power

The power supply is from a 3.7v 2000mAh LiPo battery (costing $15) and small 5v 1.5W solar panel (costing $10). Both the battery and solar panel are connected to a dfrobot sunflower battery charger and 5v regulator (costing $12). This device simultaneously charges the battery from the solar panel and provides a 5v regulated supply to the rest of the system. The sunflower has a constant 5v regulated output as well as a user controlled 5v regulated output that can be witched on or off via a control wire. The constant output is used to power the ATMEGA328P and the user controlled output is used to power the ESP8266 wifi board via the DCDC converter.

NOTE: as described above and in the software section, there were issues with transient current draw from the ESP8266 WiFi board apon power on. These where handled via the decoupling capacitors accross the VCC-GND lines of the ATMEGA328P.

Changing the BOD Level

As described above and in the software section, there were issues with transient current draw from the ESP8266 WiFi board apon power on. These where handled via the decoupling capacitors accross the VCC-GND lines of the ATMEGA328P. However, if you want to change the BOD reset level, you need to use some software and an Arduino Uno to set the extended byte fuse in the ATMEGA328P as follows:

Fig.x - ATMEGA328P Connection to Set the BOD Level

Fig.x - ATMEGA328P Connection to Set the BOD Level

Then use this guide and software to do it via the windows command line, [4].

Communciations

The communications are performed by the ESP8266 wifi board (costing $2). This tiny board is itself a micro-controller and actually has several GPIO pins, however for this task it is simply being used as a slave wifi unit. As it runs on 3.3v as opposed to the 5v used by the ATMEGA328P, it has to be supplied via a high effiency DCDC converter which converts 5v to 3.3v (costing $8). The 9600bps baud data lines run between the ATMEGA328P and the ESP8266, which allow the ATMEGA328P to control the ESP8266 via AT commands to connect to a wifi hotspot and HTTP POST the sensor readings to a web server. The readings are then processed and stored in a database for retreival by the same webserver application apon dashboard GET request from an end-user browser.

The ESP8266 01 used in this project uses an on board omni directional antenna. This is effectively an isotropic antenna with unity gain at best. This makes deployment simple, but also means that our effective range from the nearest WiFi AP is reduced compared to a case where we had an external directional antenna that could increase the usable range from 20-30m out to 100m in LOS cases due to the reduction in target AP pathloss and increase in interferer pathloss. From testing, the tupperware enclosure and EPS foam insulation are both quite RF transparent meaning that placing the ESP8266 inside the enclosure had little impact on the RF pathloss to the target AP.

Fig.x - ESP8266 01 board

Fig.x - ESP8266 01 board

Changing the baud rate to 9600

The baud rate of the WiFi unit had to be changed from the default of 115200 bps to 9600 bps for use with Software Serial. This is a dangerous action as if the wrong command is entered, the WiFi board becomes uncontrollable (becomes bricked), requiring it to need re-burning of the booloader.

Fig.x - ESP8266 Connection for AT Commands

Fig.x - ESP8266 Connection for AT Commands

Once you have your ESP8266 connected to the GND, 3.7v, Tx and Rx pins of your Arduino Uno and the baud rate set to 115200bps, you can issue it with AT commands to test it and configure it.

You need to send the following command exactly (no spaces) to change the baud rate:

AT+UART_DEF=9600,8,1,0,0

Reburning the ESP8266 Bootloader

When the ESP8266 becomes bricked, the bootloader needs to be reburned. There are many tools out there to do this via the Arduino Uno [1][2][3]. You need to connect the ESP8266 as such:

Fig.x - ESP8266 Connection for Burning the Bootloader

Fig.x - ESP8266 Connection for Burning the Bootloader

Sensors

The various sensors employed were:

  • the wind speed and wind direction sensors, these are analog devices that need to be mounted in a location free from wind obstructions (higher structures within 10m). These turned out to be the most difficult part to obtain cheaply with the final solution being to order spare parts for a commercially avaialble product for $70 and then cutting the rj12 connector to use the bare wires. Each sensor required a ground and a pullup measurement wire.
    • the wind speed sensor (anemometer) closed a switch every half rotation of the wind sensor, this pulled the measurement wire voltage low termporarily every half rotation. To use this, the measurement wire needed to be connected to an interrupt enabled pin of the MCU. The interrupt triggered by this pin falling would then calculate the wind speed based on the time passed since the previous interrupt.
    • the wind direction sensor varied the resistance based on the direction of the sensor, so the measurement wire voltage would change depending on the direction of the sensor. Thus this was sampled by an analog input pin periodically.
Fig.x - Wind Sensor Schematic

Fig.x - Wind Sensor Schematic

  • the temperature sensor. Requiring a ground and a pullup measurement wire.
  • the ambient light sensor. Requiring a ground and a pullup measurement wire.
  • the battery level sensor requiring only one measurement wire, this was a built-in function of the sunflower power regulator.
  • the wifi rssi sensor requiring no wires as it was measured by the ESP8266 directly.

Weather Proof Housing

As the unit needed to be placed outdoors in the weather, a weather-proof enclosure was required. The enclosure needed to be:

  • water proof
  • easily servicable
  • insulated against the direct midday sun to avoid MCU/battery overheating
  • able of having a weather-proof cable entry point

The final solution to this was a premium water-tight silicone sealed click-lock tupperware container costing around $5. EPS foam was siliconed to the internal sides of the container to insulate it. A cable entry hole was burned in one side and an electrical entry boot with a downward facing pipe was siliconed in place. The solar panel was siliconed on the top of the housing as putting it inside halved the solar energy harvesting ability despite the tupperware material being transparent.

Fig.2 - Weather-Proof Housing

Fig.2 - Weather-Proof Housing

Fig.x - Weather-Proof Housing Internal

Fig.x - Weather-Proof Housing Internal

Fig.x - Weather-Proof Housing External

Fig.x - Weather-Proof Housing External

Sensor Frame

The wind speed and direction sensors needed to be mounted on a frame to get them above the surrounding obstacles. A simple wooden structure was constructed to hold the sensors as shown in the following figure.

Fig.3 - Wind Sensor Frame

Fig.3 - Wind Sensor Frame

Fig.x - Wind Sensor Frame Actual

Fig.x - Wind Sensor Frame Actual

MCU Software

The software running the MCU system is one arduino sketch written in C. The general flow of the MCU code is as follows:

  1. Startup
    • the pins are defined
    • the variables are reset
    • the watchdog timer interrupt is activated
  2. Measurement Cycle
    • activates the wind speed interrupt
    • start the measurement cycle main timer
    • start the periodic measurement timer
    • When the periodic measurement timer expires, a measurement is taken from the wind direction sensor as well as the battery, temp and light level sensors
    • Watchdog timer (WDT), the WDT interrupt will fire every 8s, if the WDT total time is below the timeout threshold, the program continues, if not, the MCU is hard reset
    • Wind Speed Interrupt => during the measurement cycle if the wind speed sensor closes the circuit on a half rotation of the wind speed sensor, it triggers the wind speed interrupt which is handled by an ISR
    • when the main measurement timer expires the MCU deactivates the wind speed interrupt and exits the measurement cycle
  3. Post Processing
    • after measuring the data, the MCU post processes it to prepare for transmission
  4. Data Communications
    • the WiFi board is powered on
    • it connects to the target AP
    • it sets up an TCP connection to the target server
    • it sends an HTTP POST request with the data payload
    • the WiFi board is powered down
  5. Prepare to Sleep the MCU
    • stop the WDT interrupt
    • calculate the sleep period from the ambient light level
  6. Sleep the MCU for the given sleep period
  7. Wake the MCU
    • reset the counters
    • start the WDT
  8. Go back to step 2
Fig.x - MCU Software Flow Chart

Fig.x - MCU Software Flow Chart

Please see Software Appendix 1 for the actual code.

Interrupts and BOD

Interrupts are used for the watch dog timer, the sleep timer, the wind speed sensor and BOD.

For the case of the WDT and sleep, the interrupt is internal and we simply need to activate and configure it. The maximum time to configure it is 8s, so to handle times longer than 8s we need to repeat it.

For the wind speed sensor, we tie the incoming signal line to an interrupt pin on the MCU and allow it to trigger an interrupt. We then handle the interrupt by processing the data in the ISR (Interrupt Serve Routine).

The BOD (Brown Out Detector) is an internal interrupt that is by default set to 2.7v and can be changed to 4.3v or disabled. The BOD essentially monitors the VCC supplied voltage to the MCU, if the voltage drops below the threshold (2.7v in our case), the BOD will hard reset the MCU. The reason for this is to prevent transient low voltage conditions from stopping the MCU oscillator clock. When this occurs the MCU can go into a hang state that the WDT can not recover from, the only way out of a hang scenario is a hard external reset by pulling the reset pin low, which requires external intervention. So the BOD is there to hard reset the MCU before this hang state occurs if the supply voltage is dropping.

NOTE: as this system was using only one power supply to power both the WiFi module and the MCU, when the WiFi module was powered on, it would draw a transient current over the capacity of the battery/power supply. This would in-turn cause a transient voltage dip in the supply voltage to the MCU. If not handled, this voltage dip was too fast for the BOD to reset the board before it went into a hang state. The solution to this was to use larger film type decoupling capacitors on both VCC-GND pins coming into the MCU. This seemed to reduce the voltage dip and possibly slow it down giving the BOD time to detect the voltage drop and reset the board before any hang state occured.

Webserver and Database

The web server and database could be anywhere and any platform. As a raspberry pi was available, it was connected to a wifi hotspot, port forwarding enabled on the AP and a flask webserver was setup along with an sqlite database on the RP.

NOTE: as the particular AP used to host the server had a fixed IP, we could access this from the internet easily.

The software for the webserver is simply a flask application running on Python. The database uses sqlite which comes with Python. To create a new database, you simply create a new database from the command line and a new database file is created, then you create a table in that database which you can then access from python code.

Please see software appendix 2 for the actual code.


Results and Discussion

Power Harvesting

From the data shown in the following figure, it is clear that the small 1.5W solar panel has enough harvesting ability to run the sensor unit indefintely. The battery voltage in the figure is the terminal voltage of the LiPo battery, it it has a low voltage cut off of ~ 2.8v, if the voltage drops below this, the battery shuts off to avoid damage. At full charge the battery reaches around 4.4v. Thus anywhere in-between 3v to 4.4v is the normal operating region indicating a level of battery charge. The voltage will drop evenly until it reaches around 3v where it starts dropping fast.

During the stormy day of the breif period of sunshine charged up the battery above 4v and then it maintained that level for the rest of the day with very heavy clouds, only dropping once night fell and the solar unit harversted no more energy. During the scattered cloud day (day 2), the sunlight was enough to charge the bttery to almost full. During day 3 with less cloud and a higher starting voltage, the battery was quickly charged full.

From these results, it is clear that solar harvesting is a viable option for IoT devices and offers sufficient power to run a radio communications module if used with care (careful duty cycling). If used with a larger battery (the current battery is very small) and/or a larger solar panel (the solar panel used was only 8x13cm), higher power RF communications could be deployed easily.

Lastly, it can be seen from the following figure that the MCU starts resetting at lower voltages, due to the current spike from the WiFi board starting up. This is a power supply issue and needs to be handled effectively for the unit to operate reliably.

Fig.x - Power Harvesting Results

Fig.x - Power Harvesting Results

This figure shows the LiPo typical charge curve supplied by the dfrobot battery manager.

Fig.x - Battery Charge Curve

Fig.x - Battery Charge Curve

Communications

WiFi RF communications were selected as the communications technology of choice because WiFi modules are cheap and available ($2) for the ESP8266. The issue with WiFi is mainly that it is short range and thus only available within 20-30m of an AP. What’s more WiFi is using ISM bands so it can suffer from interference in crowded areas. For a more long range solution there are several options, these were considered, but none met the cost criteria.

  • Cellular => cellular is widely available and would enable the sensor station to be deployed in a much larger range of areas. The issues are simply availability of cellular capable boards. 2G boards are the cheapest, but still cost upwards of $20. 3G-UMTS boards are $60+ and 4G-LTE boards are $100+. For this reason, cellular, while being useful, was not cosidered for this project, however it would be the best choice in a real world scenario.
  • LORA => after the terrible experience of trying to connect a lora shield to an arduino uno and then connect to the things network, this technology was not considered appropriate. The main issue is that the the things network is a shared best effort network and service quality is very patchy. Sometimes messages get though, someties not, despite having good RF. For LORA to be effective, one would be forced to buy an LORA AP and then do the RF engineering to ensure a good link between the sensor and the AP. All of this would end up costing money and time.

Finally, using a LORA type technology forces one to use very compact data payload formats. This is good for not affecting network capacity when many devices connect, but it is also troublesome. Using WiFi allowed the luxury of simply sending as much ASCII format data as was desired, so it was easier to get going.

Total Cost

While the goal was to create a cheap solution, the main sensor availability made this a difficult goal. However, the total cost of the system turned out to be:

  • MCU + ancilliary equipment ~ $15
  • WiFi ~ $2
  • Power Manager ~ $12
  • DCDC ~ $8
  • Battery ~ $15
  • Solar Panel ~ $10
  • Wind Sensors ~ $70
  • Mounting and Weather-proofing hardware ~ $10

The total cost was ~ $70 for everything excluding the wind sensors and another $70 for the wind sensors, thus the total was ~ $140, which can’t be considered that cheap.

In order to reduce the cost, the whole system should be designed into a custom pcb, requiring only a separate battery and solar panel. This would enable the use of a smaller enclosure also. However, the issue of the wind sensors is somewhat harder to solve. Building the sensors from scratch would be the most effective way of reducing cost, possibly with 3D printing[7]. Ultasonic technology could also be investigated for wind speed and direction.

Mounting Location

Being a wind sensor, it is very sensitive to mounting location. The sensor needs to be mounted above nearby obstacles and at least 10m away from larger structures, ideally much further. If these mounting rules are not adhered to, the wind speed and direction will be inaccurate. Wind direction esspecially will suffer from eddies and give variable readings.

Fig.x - Wind Eddies

Fig.x - Wind Eddies

Usability of the data

The optimal way to expose the data to the end user is via a webserver and some kind of HTML-CSS-Javascript backed functionality. HTML-CSS-Javascript offers leading edge graphics and user interactivity. Ideally the data should be available via a dashboard where all the information is available in one page. The current webserver only offers text based database access due to time constraints.

Fig.x - Wind Speed and Direction

Fig.x - Wind Speed and Direction

Fig.x - Wind Sensor Location

Fig.x - Wind Sensor Location


Conclusion

Making a standalone self powered IoT field device is definitely possible with readily available battery and solar technology. There are however, serious challenges relating to connectivity, cost and reliability. For this prototype it has been demonstrated that such a device can be made successfully.

It should be noted that in order to create power efficient devices, one needs to move away from user-freindly Arduino boards that effectively shield the end user from the harsh reality of the underlying electronics. When dealing with raw IC chips and multiple devices sharing power supplies, a better understanding of electronics, volt-meters and oscilloscopes is required.

Lastly, finding a location to mount the device is another challenge that may prove to be the one of most difficult to solve, as one would need the permission of the land/structure owner.

Weather sensors in particular, unfortunately, are normally relegated to the realm of hobbyists due to the lack of perceived potential monetary gain. However, some of the base technologies (weather-proofed enclosure, power self-sufficiency, radio communications) could be used as a platform for many different usage cases.


References

  1. https://www.electronicshub.org/update-flash-esp8266-firmware/
  2. https://cordobo.com/2300-flash-esp8266-01-with-arduino-uno/
  3. https://drive.google.com/open?id=1tD7IpE4rPMOWyQHP6xzjdRKjJAyeEZPj
  4. https://www.instructables.com/id/How-to-change-fuse-bits-of-AVR-Atmega328p-8bit-mic/
  5. http://www.gammon.com.au/breadboard
  6. https://www.arduino.cc/en/Tutorial/ArduinoToBreadboard
  7. https://blog.arduino.cc/2018/04/09/a-3d-printed-personal-weather-station/
  8. https://blog.kkessler.com/2012/06/21/sparkfun-weather-station/

Software Appendix 1

The actual code is shown below in logical sections. Part 1: Setup commands

#include <avr/sleep.h>
#include <avr/wdt.h>
#include <SoftwareSerial.h>
SoftwareSerial esp(9, 10);  //RX,TX

#define comms_rx_pin 0
#define comms_tx_pin 1
#define w_speed_itr_pin 3
#define comms_pwr_pin 5
#define w_speed_pwr_pin 6
#define w_dir_pwr_pin 7
#define status_led_pin 8
#define light_sensor_meas_pin A0
#define w_dir_meas_pin A1
#define bat_meas_pin A2
#define temp_meas_pin A3
#define hum_meas_pin A4
#define KMPHPP 2.4    //kmph per pulse of the wind speed meter
#define BOUNCE_GUARD 500   //ignore wind speed pulses shorter than this in u-secs
#define VREF 5    ///vref should be = 5v = Vcc
#define TEMPCONV 10    //the conversion for temp
#define HUMCONV 10    //the conversion for temp

int test_cnt = 0;
bool testing = false;   //set to true for testing
bool wind_test = false;   //set to true for testing
bool startup_wifi_flag = false;

int i;    //global counter var, do nto use in functions

//for wifi
String server_ip = "xxx.xxx.xxx.xxx";
int server_port = 5000;       
String post_uri = "/data";
String ssid = "ssidssidssid";
String pwd = "pwdpwdpwdpwd";

//timing vars
int sleep_s = 270; //270;   //length of rest time
int sleep_s_min = 270;//270;      //min rest time for adaptive rest
int sleep_s_max = 870;//870;      //max rest time for adaptive rest
int sleep_s_delta = 1000;//200;    //rest time change rate for adaptive rest
int sensor_read_s = 30;//30;   //how long is a measurement cycle (around 15-30s is ok)

//wdt vars
int wdt_timeout_s = sensor_read_s + 200;//300;   // set this to the sensor read time and the max acceptable connect time
int wdt_timeout_reps = wdt_timeout_s/8;
volatile int wdt_timeout_rep_cnt = 0;

//other timing vars
unsigned long meas_cycle_start = millis();     //start time of the current meas cycle
unsigned long time_now = 0;        //holds the current time
unsigned long w_dir_cycle_start = 0;  //start time of the current wind direction cycle
int w_dir_cycle_length_s = 5;   //how often to read the wind direction sensor

//sleep vars
bool sleep_flag = false;
int sleep_reps = sleep_s/8;   //how many 8s rest cycles are required

String result;
bool trouble_flag = false;      //used to detect bad connectivity and wait it out
bool connected_flag = false;
bool http_ok_flag = false;

//light sensor vars
double light_sensor_thd = 0.5;   //0.5;   //in volts
double light_sensor_v = 0.0;   //in volts

//battery meter vars
int bat_meas_cnt = 0;   //how many bat measurements
double bat_v = 0.0; //in volts

//temp vars, temp in deg.C
int temp_meas_cnt = 0;   //how many temp measurements
double temp = 0.0;

//hum vars
int hum_meas_cnt = 0;   //how many hum measurements
double hum = 0.0;

//vars for use in the wind speed interupt routine
volatile unsigned long t_prev = 0.0;  //time of the previous pulse
volatile int pulse_cnt = 0;   //how many pulses
volatile double w_speed_mean = 0.0;
volatile double w_speed_sd = 0.0;

//wind direction vars
int w_dir_meas_cnt = 0;    //how many wind dir measurements collected in this cycle
double w_dir_mean_x = 0.0;
double w_dir_sd_x = 0.0;
double w_dir_mean_y = 0.0;
double w_dir_sd_y = 0.0;
double w_dir_mean = 0.0;
double w_dir_sd = 0.0;

//These numbers are for a 5v Vref and a 5V Vcc and a 10K pullup resistor
int w_dir_raw_range[] = {66,84,92,127,184,244,287,406,461,600,631,702,786,827,889,946};
int w_dir_az[] = {1125,675,900,1575,1350,2025,1800,225,450,2475,2250,3375,0,2925,3150,2700};

Part 2: the interrupt handling code for the watchdog timer and sleep as well as the wind speed sensor.

//ISR for the wind speed sensor
void on_wind_speed_pulse_interupt(){
  //this processes the wind speed pulse from the sensor, the pulses occur once per sensor rotation
  unsigned long t_now = micros();
  
  //check if we are starting the meas cycle again
  if (t_prev == 0){
    t_prev = t_now;      
  } 
  else{
    //get the duration since last function call
    unsigned long duration = t_now - t_prev;
    //only process if the duration is longer than the bounce protect, needed to filter out sensor switch bounce
    if (duration > BOUNCE_GUARD) {
      t_prev = t_now;   //only update t_prev if we accept the point a legit pulse
      pulse_cnt++;
      w_speed_mean += KMPHPP/((double)duration/1000000.0);
      w_speed_sd += pow(KMPHPP/((double)duration/1000000.0), 2);
    }
  }
}


//ISR for wdt and for sleep
ISR (WDT_vect) {
  if (!sleep_flag) {
    //this is for the watchdog timer case
    wdt_disable();
    wdt_timeout_rep_cnt++;
    if (wdt_timeout_rep_cnt > wdt_timeout_reps){
      //get here if the watchdog timer times out
      if (testing) Serial.println("TIMEOUT RESTARTING!!!");
      digitalWrite(comms_pwr_pin, LOW);
      wdt_enable(0); //we have timedout, so we need to reset
    }
    else{
      wdt_enable(9);   //set stadnard 8s wdt
      set_wdt_int_8s();   //modify the 8s wdt
    }
  }
}


void set_wdt_int_8s() {
  //setup the watch dog timer
  cli();
  MCUSR &= ~(1<<WDRF);
  WDTCSR = (1<<WDCE) | (1<<WDE);  // Set WDCE and WDE to enable changes.
  WDTCSR = (1<<WDIE) | (0<<WDE) | (1<<WDP3) | (0<<WDP2) | (0<<WDP1) | (1<<WDP0);
  sei();
}


//sleep interrupt setup, does the same thing as the previous function
void sleep_settings_activate(){
  cli();
  MCUSR &= ~(1<<WDRF);
  WDTCSR = WDTCSR | B00011000; 
  WDTCSR = B00100001;
  WDTCSR = WDTCSR | B01000000;    // Enable the watchdog timer interupt.
  sei();
}


void sleep_mcu(int reps)   {
  //actually do the sleep
  if (testing) Serial.println("starting sleep");
  int j=0;
  sleep_settings_activate();
  sleep_flag = true;
  for (j=0;j<reps;j++){
    set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode.
    sleep_enable(); // Enable sleep mode.
    sleep_mode(); // Enter sleep mode, this is a built in function
    //######################################
    // After waking from watchdog interrupt the code continues to execute from this point.
    //######################################
    sleep_disable(); // Disable sleep mode after waking, this is a built in function
  }
  sleep_flag = false;
  if (testing) Serial.println("sleep finished");
}

Part 3: Variable reset functions and some trig helper functions

void reset_vars(){
  //reset all vars on MCU wakeup
  pulse_cnt = 0;
  w_speed_mean = 0;
  w_speed_sd = 0;
  t_prev = 0;

  w_dir_meas_cnt = 0;
  w_dir_mean_x = 0;
  w_dir_mean_y = 0;
  w_dir_sd_x = 0;  
  w_dir_sd_y = 0;
  w_dir_mean = 0;
  w_dir_sd = 0;

  temp = 0;
  hum = 0;
  bat_v = 0;
  bat_meas_cnt = 0;
  temp_meas_cnt = 0;
  hum_meas_cnt = 0;
  
  meas_cycle_start = millis();
  w_dir_cycle_start = meas_cycle_start;
}


//some trig related functions for the wind direction
double correct_deg(double deg){
  if (deg < 0){
    return deg + 360.0;   
  }
  if (deg >= 360){
    return deg - 360.0;   
  }
  return deg;
}

double rad2deg(double rad){
  return correct_deg(rad*(180.0/M_PI));  
}

double deg2rad(double deg){
  return M_PI*deg/180.0;  
}

Part 4: Measure the Wind Direction, Temperature, Battery Level and Light Level

void get_w_direction(){
  if (testing) Serial.println("Doing Wind Dir...");
  double az_deg = -1.0;
  int k = 0;
  
  //take a measurement
  analogReference(VREF);
  digitalWrite(w_dir_pwr_pin, HIGH);
  delay(500);
  long w_dir_raw_meas = 0;
  //not sure why he repeat reads this and then just takes the last one
  for(k=0;k<10;k++){
    w_dir_raw_meas = analogRead(w_dir_meas_pin);
    delay(50);
  }
  digitalWrite(w_dir_pwr_pin, LOW);

  //this looks for the closest match, if it finds one within 4 points of the nominal, it goes for it
  //otherwise, it just finds the closest and returns that
  int min_delta=1024;
  int min_index=8;
  for (k=0;k<16;k++){
    int delta = min(abs(w_dir_raw_meas - w_dir_raw_range[k]), abs(w_dir_raw_meas+1023 - w_dir_raw_range[k]));
    if (delta < 4){
       az_deg = (double)w_dir_az[k]/10.0;
       break;
    }
    if (delta < min_delta){
      min_delta = delta;
      min_index = k;
    }
  }
  if (az_deg == -1.0){
    az_deg = (double)w_dir_az[min_index]/10.0;
  }

  //process the result
  w_dir_mean_x += sin(deg2rad(az_deg));
  w_dir_mean_y += cos(deg2rad(az_deg));
  w_dir_sd_x += pow(w_dir_mean_x,2);
  w_dir_sd_x += pow(w_dir_mean_y,2);
  w_dir_meas_cnt++;
}

void read_bat_lev(){
  int bat_meas = analogRead(bat_meas_pin);
  bat_meas_cnt++;
  bat_v += (double)VREF*((double)bat_meas+1.0)/1024.0;  
}


void read_temp(){
  //read the raw value
  int temp_meas = analogRead(temp_meas_pin);
  temp_meas_cnt++;
  temp += (double)TEMPCONV*(double)(temp_meas+1)/1024.0;
}


void read_hum(){
  //read the raw value
  int hum_meas = analogRead(hum_meas_pin);
  hum_meas_cnt++;
  hum += (double)HUMCONV*(double)(hum_meas+1)/1024.0;
}

Part 5: Post Process the measured data

void post_meas_cycle_calcs(){
  //this does the wind speed
  if (pulse_cnt == 0){
    //wind speed is too low to accurately measure
    w_speed_mean = 0;
    w_speed_sd = 0;
  }
  else{
    if (pulse_cnt < 2){
      //wind speed is too low to accurately measure
      w_speed_mean = w_speed_mean / (double)pulse_cnt;
      w_speed_sd = 0;
    }
    else{
      w_speed_mean = w_speed_mean / (double)pulse_cnt;
      w_speed_sd = sqrt((1/((double)pulse_cnt-1))*(w_speed_sd - (double)pulse_cnt*pow(w_speed_mean,2)));
    }
  }

  //this does the wind dir
  double resultant = (w_dir_mean_x/(double)w_dir_meas_cnt)/(w_dir_mean_y/(double)w_dir_meas_cnt);
  w_dir_mean = rad2deg(atan2(w_dir_mean_x, w_dir_mean_y));
  w_dir_sd = -1   // could not get it to make sense
  
  //this does the temp
  //temp = temp/(double)temp_meas_cnt;
  temp = -1.0;

  //this does the hum
  //hum = hum/(double)hum_meas_cnt;
  hum = -1.0;

  //this does the bat_lev
  bat_v = bat_v/(double)bat_meas_cnt;
}

Part 6: Adjust the sleep period

void adjust_sleep_cycle_length(){
  // read the value from the sensor, returns int from 0-1023 maps from 0 to vref(5v):
  int light_sensor_meas = analogRead(light_sensor_meas_pin);
  
  if (!trouble_flag){
    //adjust the sleep time based on the abient light, longer for lower light, shorter for more light
    light_sensor_v = (double)VREF*((double)light_sensor_meas + 1.0)/(double)1024;
    if (light_sensor_v <= light_sensor_thd){
      sleep_s = min(sleep_s_max, sleep_s + sleep_s_delta);
    }
    if (light_sensor_v > light_sensor_thd){
      sleep_s = max(sleep_s_min, sleep_s - sleep_s_delta);
    }  
  }
  else{
    int mult_factor = 1;
    //we increase the sleep to max quickly
    sleep_s = min(sleep_s_max, sleep_s + mult_factor*sleep_s_delta);
  }
}

Part 7: WiFi handling functions => Setup, Connect, Post, Wait for Response

void reset_wifi() {
  if (testing) Serial.println("resetting wifi board");
  esp.println("AT+RST");
  get_result_fast(5000);
}


int setup_wifi_full(){
  //test the cipmux setting
  if (testing) Serial.println("setting up wifi board");
  esp.println("AT+CIPCLOSE");
  get_result_fast(500);
  esp.println("AT+CIPMUX=0");   // 0 for single TCP connection mode, 1 for multi TCP connection mode
  get_result_fast(500);
  esp.println("AT+CIPMODE=0");   // 0 for normal transfer mode
  get_result_fast(500);

  //these commands are stored in memory, so you only need once
  esp.println("AT+CWMODE_DEF=1");   // 1 for wifi client mode
  get_result_fast(500);
  esp.println("AT+CWAUTOCONN=1");   //set to 0 to not auto connect to ap on power on 
  get_result_fast(500);

  //use this to set the baud rate
  /*
  digitalWrite(comms_pwr_pin, HIGH);
  esp.begin(115200);  //this assumes default baud rate is used by the module
  delay(1000);
  esp.println("AT+UART_DEF=9600,8,1,0,0");
  delay(1000);
  esp.end();
  delay(1000);
  esp.begin(9600);  
  delay(3000);
  digitalWrite(comms_pwr_pin, LOW);
  */

  //only need this once to set params, then do not need
  // board will automatically connect to the stored hotspot after this
  connect_wifi();

  //all ok
  return 1;
}


int setup_wifi_fast(){
  //test the cipmux setting
  if (testing) Serial.println("setting up wifi board");
  esp.println("AT+CIPCLOSE");
  get_result_fast(100);
  esp.println("AT+CIPMUX=0");   // 0 for single TCP connection mode, 1 for multi TCP connection mode
  get_result_fast(100);
  esp.println("AT+CIPMODE=0");   // 0 for normal transfer mode
  get_result_fast(100);
  //all ok
  return 1;
}


int connect_wifi() {
  /*list ap signals, for testing
  esp.println("AT+CWLAPOPT=1,22");
  get_result_fast(2000);
  esp.println("AT+CWLAP");
  get_result_fast(5000);
  Serial.println(result);
  
  esp.println("AT+CIPSTATUS");
  get_result_fast(2000);
  if (testing) Serial.println("Getting connection status...");
  if (testing) Serial.println(result);
  if (result.indexOf("STATUS:2")!=-1 | result.indexOf("STATUS:3")!=-1 | result.indexOf("STATUS:4")!=-1 | result.indexOf("WIFI CONNECT")!=-1){
    if (testing) Serial.println("Disconnecting from current AP");
    esp.println("AT+CWQAP");
    get_result_fast(3000);
  }
  */

  if (testing) Serial.println("Disconnecting from current AP");
  esp.println("AT+CWQAP");
  get_result_fast(3000);

  //connect to the desired ssid
  if (testing) Serial.println("Connecting to target AP");
  esp.println("AT+CWJAP_DEF=\"" + ssid + "\",\"" + pwd + "\"");
  //get_result_fast(5000);
  get_result_slow(4000, 3);
  if (result.indexOf("OK")!=-1 | result.indexOf("WIFI CONNECTED")!=-1){
    if (testing) Serial.println("Connect OK");
    if (testing) Serial.println("result: " + result);
    return 1;    //connect ok
  }
    
  //could not connect error
  if (testing) Serial.println("Could Not Connect Error");
  if (testing) Serial.println("result: " + result);
  return 0;
}




int http_post(){
  //testing
  /*esp.println("AT+CWLAPOPT=1,127");
  delay(1000);
  esp.println("AT+CWLAP");
  esp.println("AT+CIFSR");
  get_result_fast(5000);
  Serial.println(result);
  */
  
  //set the data vals to send
  //get the RSSI
  String rssi = "-1";
  esp.println("AT+CWJAP?");
  get_result_fast(1000);
  if (result.indexOf(",-")!=-1){
    rssi = result.substring(result.indexOf(",-")+1, min(result.length(),result.indexOf(",-")+5));
    rssi.trim();
  }
  String data_vals = "wspd_mean=" + (String)w_speed_mean + 
              "&wspd_sd=" + (String)w_speed_sd + 
              "&wdir_mean=" + (String)w_dir_mean + 
              "&wdir_sd=" + (String)w_dir_sd +
              "&temp=" + (String)temp +
              "&hum=" + (String)test_cnt +   //hum + 
              "&light_lev=" + (String)light_sensor_v +
              "&batt_lev=" + (String)bat_v + 
              "&rssi=" + rssi;
  if (testing) Serial.println(data_vals);

  //start a TCP connection.
  if (testing) Serial.println("Establish TCP");
  esp.println("AT+CIPSTART=\"TCP\",\"" + server_ip + "\"," + server_port + ",0");  //last number is the TCP keep alive counter in s, set to 0 to disable
  get_result_fast(3000);   //5000;
  if (testing) Serial.println(result);
  if (result.indexOf("ERROR") != -1){   // && result.indexOf("no ip") != -1){
    return 0;
  }
  
  //test that we activated the TCP or it is already activated, then we are good to proceed
  if (result.indexOf("OK")!=-1 || result.indexOf("ALREADY")!=-1){ 
    String cmd1 = "POST " + post_uri + " HTTP/1.1";
    String cmd2 = "Host: " + server_ip + ":" + (String)server_port;
    String cmd3 = "Content-Type: application/x-www-form-urlencoded";
    String cmd4 = "Content-Length: " + (String)data_vals.length();
    String cmd5 = "";

    //need to add the carriage return and new line (2 chars onto each line)
    int len = cmd1.length() + 2 + 
              cmd2.length() + 2 + 
              cmd3.length() + 2 + 
              cmd4.length() + 2 + 
              cmd5.length() + 2 + 
              data_vals.length() + 2;
          
    //determine the number of caracters to be sent.
    if (testing) Serial.println("Sending HTTP Msg");
    esp.print("AT+CIPSEND=");
    esp.println(len);
    //flush rx buffer
    get_result_fast(1000);   //2000;
    //send request
    esp.println(cmd1);
    delay(300);    
    esp.println(cmd2);    
    delay(300);    
    esp.println(cmd3);    
    delay(300);    
    esp.println(cmd4);    
    delay(300);    
    esp.println(cmd5);    
    delay(300);    
    esp.println(data_vals);    
    delay(3000);    
    // close the connection
    esp.println("AT+CIPCLOSE");
    get_result_fast(1000);   //5000;
    if (result.indexOf("ERROR")==-1){
    //if (result.indexOf("OK")!=-1 | result.indexOf("Recv")!=-1 | result.indexOf((String)len)!=-1){
      if (testing) Serial.println(result);
      return 1;    //was send ok
    }
  }

  //send not successfull
  if (testing) Serial.println("Send Fail");
  if (testing) Serial.println(result);
  return 0;    //bad sending result error
}


//gets the result from the serial rx
String get_result_slow(int del, int cnt){
  int k;
  String res_new;
  
  result = "";
  delay(del);
  if (trouble_flag) delay(15000);
  for (k=0;k<20;k++){
    res_new = esp.readString();
    if (res_new == "") {
      cnt = cnt - 1;
      if (cnt == 0) break;
    }
    result += res_new;
    delay(1000);
  }
}

//gets the result from the serial rx
void get_result_fast(int start){
  result = "";
  delay(start);
  while(esp.available()){
    result = esp.readString();
    delay(500);
  }
}

Part 8: The actual Setup and Loop Functions to tie everything together

void setup()
{
  if (testing) Serial.begin(9600);
  if (testing) Serial.println("###########START############");
  pinMode(status_led_pin, OUTPUT);
  for (i=0;i<10;i++){
    digitalWrite(status_led_pin, HIGH);
    delay(100);
    digitalWrite(status_led_pin, LOW);
    delay(100);
  }

  //wind dir sensor setup
  pinMode(w_dir_pwr_pin, OUTPUT);
  digitalWrite(w_dir_pwr_pin, LOW);    //off the sensor initally

  //wind speed sensor setup
  //use the internal 20K pullup method, this is essentially the state where the speed sensor switch is open
  //everytime the wind speed meter rotates one turn, it closes the circuit to ground temporarily thus pulling the interupt pin down
  if (!wind_test){
    pinMode(w_speed_pwr_pin, INPUT_PULLUP);   
    attachInterrupt(digitalPinToInterrupt(w_speed_itr_pin), on_wind_speed_pulse_interupt, FALLING);
    interrupts();    //ensure interupts are enabled
  }

  //setup wdt
  wdt_enable(9);
  set_wdt_int_8s();
  wdt_timeout_rep_cnt = 0;    //start the watchdog timer

  //wifi setup
  pinMode(comms_pwr_pin, OUTPUT);
  digitalWrite(comms_pwr_pin, LOW);
  esp.begin(9600);
  if (startup_wifi_flag){
    digitalWrite(comms_pwr_pin, HIGH);
    delay(1000);
    setup_wifi_full();
    digitalWrite(comms_pwr_pin, LOW);
    delay(1000);
  }

  //reset timers
  time_now = millis();
  w_dir_cycle_start = time_now;
  meas_cycle_start = time_now;
}



void loop() {
  //get the current time
  time_now = millis();
  //test if we need to finish the current meas cycle
  if (time_now - meas_cycle_start > (unsigned long)sensor_read_s*1000){
    //deactivate the wind speed sensor
    detachInterrupt(digitalPinToInterrupt(w_speed_itr_pin));
    pinMode(w_speed_pwr_pin, OUTPUT);   
    digitalWrite(w_speed_pwr_pin, LOW);
   
    //#############################################3
    //process measurements  
    if (testing) Serial.println("Data Processing");
    if (!wind_test){
      post_meas_cycle_calcs();
    }
    else{
      //just randomly make up soome numbers for testing
      w_speed_mean = random(0,30);
      w_speed_sd = random(0-6);
      w_dir_mean = random(0,359);
      w_dir_sd = random(0-10);
      temp = random(0,45);
      hum = random(0,100);
    }

    //#############################################3
    //activate the comms and send
    if (testing) Serial.println("Activating Comms");
    digitalWrite(comms_pwr_pin, HIGH);
    delay(2000);  //give it time to connect
    setup_wifi_fast();
    
    //try to connect 3 times, then go into connectivity trouble mode where we wait it out with longer sleep intervals
    http_ok_flag = false;
    if (!trouble_flag){
      //try to post
      for (i=0;i<5;i++){
        if (http_post() == 1){
          http_ok_flag = true;
          break;
        }
      }
      if (!http_ok_flag){
        //could connect but couldn not send data
        trouble_flag = true;  
        if (testing) Serial.println("HTTP fail, Trouble Flag Has Been Activated");
      }
    }
    else{
      //we are in trouble mode, so try only once and then give up
      if (connect_wifi() == 1){
        if (http_post() == 1){
          trouble_flag = false;
          if (testing) Serial.println("Trouble Flag Has Been Deactivated");
        }
      }
    }
    if (testing) Serial.println("Deactivating Comms");
    digitalWrite(comms_pwr_pin, LOW);
    delay(500);
    
    //#############################################3
    //sleep the board
    if (testing) Serial.println("Sleeping the Board");
    adjust_sleep_cycle_length();   //set the sleep time
    wdt_disable();   //disable the wtc function
    sleep_reps = sleep_s/8;  //recalc_sleep_reps  
    delay(300);
    sleep_mcu(sleep_reps);   //go to sleep

    //#############################################3
    //prep the board after wakeup
    test_cnt++;
    digitalWrite(comms_pwr_pin, LOW);
    delay(500);
    if (testing) Serial.println("Waking Processor, Starting Meas Cycle");
    wdt_enable(9); 
    set_wdt_int_8s();   
    wdt_timeout_rep_cnt = 0;    //start the watchdog timer
    reset_vars();
    if (!wind_test){
      pinMode(w_speed_pwr_pin, INPUT_PULLUP);  
      delay(100); 
      attachInterrupt(digitalPinToInterrupt(w_speed_itr_pin), on_wind_speed_pulse_interupt, FALLING);
    }
  }

  else{
    //check if we need to go in and measure wind direction and temp
    if (time_now - w_dir_cycle_start > (unsigned long)w_dir_cycle_length_s*1000){
      if (!wind_test){
        get_w_direction();
        read_temp();
        read_hum();
        read_bat_lev();
      }
      w_dir_cycle_start = time_now;  
    }   
  }
}

Software Appendix 2

These are the SQL and data handling functions running on the RP.

from flask import Flask, url_for, escape, g, request, render_template, send_from_directory, jsonify
from flask_cors import CORS, cross_origin
import sqlite3
import json
from datetime import datetime as dt

def run_sql(db, sql, get_response=False, header=False):
    response = 'ok'
    try:
        with sqlite3.connect(db) as db_conn:
            cur = db_conn.cursor()
            cur.execute(sql)
        if get_response:
            response = cur.fetchall()
        
        if header:
            cols = [tuple([description[0] for description in cur.description])]
            return cols + response
        else:
            return response
    except Exception as e:
        return 'error: ' + str(e)
    
def data_processor(data, schema):
    #converts the incoming dict to a string for db update
    #check data format is ok
    if len(data) != len(schema_list): return None
    #note: dt.now() gives the current time, dt.utcfromtimestamp(0) gives the time at 0:0:0 1/1/1970
    dt_now = dt.now()
    datetime_str = dt_now.strftime('%d/%m/%y %H:%M:%S')
    secs_since_1970 = int((dt_now - dt.utcfromtimestamp(0)).total_seconds())
    data_str = '{0},"{1}"'.format(secs_since_1970, datetime_str)
    for item in schema:
        if item not in data: return None
        data_str += "," + str(data[item])
    return data_str

This creates the web app and defines the homepage

app = Flask(__name__, static_url_path='')
CORS(app)
db_path = '/home/pi/weather_ap/weather.db'
schema_list = ['wspd_mean', 'wspd_sd', 'wdir_mean', 'wdir_sd', 'temp', 'hum', 'light_lev', 'batt_lev','rssi'] 
@app.route('/')
def homepage():
    return send_from_directory('static', 'index.html')

This is to handle POST requests from the MCU

@app.route('/data', methods = ['POST'])
def rx_data():
    if request.method != 'POST': 
        return 'nok'
        
    #this will read in all the post data as a dict
        data = request.form
    print(data)
    #convert it to a string
    data_str = data_processor(data, schema_list)
    if data_str is None: return 'nok'
    #build the sql command and send it
    sql = 'insert into data values({0});'.format(data_str)
    response = run_sql(db_path, sql, get_response=False, header=False)    
    return response

This is to handle user requests from the database

#usage: http://ip:port/db/24  ... or use last for the most recent value
@app.route('/db/<hrs>')
def show_db(hrs):
    if hrs == 'last':
        sql = 'SELECT * FROM data ORDER BY datetime_sec DESC LIMIT 1;'
    else:
        #this will show the data for the last N hours
        secs_since_1970 = int((dt.now() - dt.utcfromtimestamp(0)).total_seconds())
        time_thd = secs_since_1970 - 3600*int(hrs)
        sql = 'select * from data where datetime_sec > {0};'.format(time_thd)
    response = run_sql(db_path, sql, get_response=True, header=True)    
    return render_template("sql_select.html", data = response);
        #return jsonify(response)

This actually runs the flask webserver

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)